1 module query;
2 
3 import std.algorithm;
4 import std.array;
5 import std.conv;
6 import std.string;
7 import language;
8 import node;
9 import tree_visitor;
10 import other;
11 import libc : TSQuery, TSQueryError, TSQueryMatch, TSQueryCursor,
12   TSQueryCapture, TSQueryPredicateStep;
13 
14 /// A particular `Node` that has been captured with a particular name within a `Query`.
15 struct QueryCapture
16 {
17   /// The `Node` that was captured.
18   Node node;
19   /// the index
20   int index;
21   /// The name of the capture.
22   string name;
23 }
24 
25 /// A match of a `Query` to a particular set of `Node`s.
26 struct QueryMatch
27 {
28   /// The id 
29   uint id;
30   /// the pattern index
31   uint pattern_index;
32   /// the captures array
33   QueryCapture[] captures;
34 }
35 
36 /// A sequence of `QueryMatch`es associated with a given `QueryCursor`.
37 struct QueryIterator
38 {
39   import libc : ts_query_cursor_new, ts_query_cursor_delete, ts_query_cursor_exec,
40     ts_query_cursor_next_match, ts_query_cursor_set_byte_range,
41     ts_query_cursor_set_point_range;
42 
43   private Query* query;
44   private Node* node;
45 
46   private TSQueryCursor* cursor;
47 
48   @disable this(this);
49 
50   /// Create a new `QueryIterator` for the given `Query` and `Node`.
51   this(Query* query, Node* node)
52   {
53     this.query = query;
54     this.node = node;
55     cursor = ts_query_cursor_new();
56     ts_query_cursor_exec(cursor, query.tsquery, node.tsnode);
57   }
58 
59   /// Create a new `QueryIterator` for the given `Query` and `Node` and given byte range.
60   this(Query* query, Node* node, uint min, uint max)
61   {
62     this(query, node);
63     set_byte_range(min, max);
64   }
65 
66   /// Create a new `QueryIterator` for the given `Query` and `Node` and given point range.
67   this(Query* query, Node* node, Point min, Point max)
68   {
69     this(query, node);
70     set_point_range(min, max);
71   }
72 
73   ~this()
74   {
75     ts_query_cursor_delete(cursor);
76   }
77 
78   /**
79      * Adjusts the range in which the query will apply.
80      * `min` and `max` are byte offsets.
81      */
82   void set_byte_range(uint min, uint max)
83   {
84     ts_query_cursor_set_byte_range(cursor, min, max);
85   }
86 
87   /**
88      * Adjusts the range in which the query will apply.
89      * `min` and `max` are Point offsets.
90      */
91   void set_point_range(Point min, Point max)
92   {
93     ts_query_cursor_set_point_range(cursor, min, max);
94   }
95 
96   /// Returns the next `QueryMatch` in the sequence.
97   int opApply(scope int delegate(QueryMatch) dg)
98   {
99     TSQueryMatch match;
100     int result = 0;
101     while (ts_query_cursor_next_match(cursor, &match))
102     {
103       auto captures = match.captures[0 .. match.capture_count].map!(
104           (capture) => QueryCapture(Node(capture.node),
105           capture.index, query.capture_name(capture))).array;
106       result = dg(QueryMatch(match.id, match.pattern_index, captures));
107       if (result)
108         break;
109     }
110     return result;
111   }
112 }
113 
114 /// An error that occurred when trying to create a `Query`.
115 class QueryException : Exception
116 {
117   /// the internal TSQueryError error
118   TSQueryError error;
119   /// Create a new `QueryException` with the given `TSQueryError`.
120   this(TSQueryError error)
121   {
122     super("QueryException: " ~ error.to!string);
123     this.error = error;
124   }
125 }
126 
127 /// A query to retrieve information from the syntax tree.
128 struct Query
129 {
130   import libc : ts_query_new, ts_query_delete, ts_query_pattern_count,
131     ts_query_capture_count, ts_query_start_byte_for_pattern,
132     ts_query_predicates_for_pattern,
133     ts_query_step_is_definite,
134     ts_query_capture_name_for_id,
135     ts_query_disable_capture, ts_query_disable_pattern,
136     ts_query_string_count, ts_query_string_value_for_id;
137 
138   /// The underlying `TSQuery`
139   TSQuery* tsquery;
140   /// The language of the query
141   private Language language;
142 
143   @disable this(this);
144 
145   /// Create a new query from a string containing one or more S-expression
146   /// patterns.
147   ///
148   /// The query is associated with a particular language, and can only be run
149   /// on syntax nodes parsed with that language. References to Queries can be
150   /// shared between multiple threads.
151   this(Language language, string queryString)
152   {
153     import std.conv;
154 
155     this.language = language;
156     uint errOffset = 0;
157     TSQueryError errType;
158     this.tsquery = ts_query_new(language.tslanguage, queryString.toStringz,
159         queryString.length.to!uint, &errOffset, &errType);
160 
161     if (errOffset != 0)
162     {
163       throw new QueryException(errType);
164     }
165   }
166 
167   ~this()
168   {
169     ts_query_delete(tsquery);
170   }
171 
172   /**
173      * Execute a query over an entire node.
174      *
175      * The caller may iterate over the result to receive a series of
176      * `QueryMatch` results.
177      */
178   QueryIterator exec(Node node)
179   {
180     return QueryIterator(&this, &node);
181   }
182 
183   /**
184      * Execute a query between given start and end byte offsets.
185      *
186      * The caller may iterate over the result to receive a series of
187      * `QueryMatch` results.
188      */
189   QueryIterator exec(Node node, uint min, uint max)
190   {
191     return QueryIterator(&this, &node, min, max);
192   }
193 
194   /**
195      * Execute a query between given start and end `Points`.
196      *
197      * The caller may iterate over the result to receive a series of
198      * `QueryMatch` results.
199      */
200   QueryIterator exec(Node node, Point min, Point max)
201   {
202     return QueryIterator(&this, &node, min, max);
203   }
204 
205   /**
206      * Get the number of patterns in the query.
207      */
208   int pattern_count() @nogc nothrow
209   {
210     return ts_query_pattern_count(tsquery);
211   }
212 
213   /**
214      * Get the number of captures in the query.
215      */
216   int capture_count() @nogc nothrow
217   {
218     return ts_query_capture_count(tsquery);
219   }
220 
221   /**
222      * Get the number of string literals in the query.
223      */
224   int string_count() @nogc nothrow
225   {
226     return ts_query_string_count(tsquery);
227   }
228 
229   /**
230      * Get the byte offset where the given pattern starts in the query's source.
231      *
232      * This can be useful when combining queries by concatenating their source
233      * code strings.
234      */
235   int start_byte_for_pattern(uint patternId) @nogc nothrow
236   {
237     return ts_query_start_byte_for_pattern(tsquery, patternId);
238   }
239 
240   /**
241      * Get all of the predicates for the given pattern in the query.
242      *
243      * The predicates are represented as a single array of steps. There are three
244      * types of steps in this array, which correspond to the three legal values for
245      * the `type` field:
246      * - `TSQueryPredicateStepTypeCapture` - Steps with this type represent names
247      *    of captures. Their `value_id` can be used with the
248      *   `ts_query_capture_name_for_id` function to obtain the name of the capture.
249      * - `TSQueryPredicateStepTypeString` - Steps with this type represent literal
250      *    strings. Their `value_id` can be used with the
251      *    `ts_query_string_value_for_id` function to obtain their string value.
252      * - `TSQueryPredicateStepTypeDone` - Steps with this type are *sentinels*
253      *    that represent the end of an individual predicate. If a pattern has two
254      *    predicates, then there will be two steps with this `type` in the array.
255      */
256   const(TSQueryPredicateStep)[] predicates_for_pattern(uint patternId) @nogc nothrow
257   {
258     uint len;
259     auto ptr = ts_query_predicates_for_pattern(tsquery, patternId, &len);
260     return ptr[0 .. len];
261   }
262 
263   /**
264      * Check if a given step in a query is 'definite'.
265      *
266      * A query step is 'definite' if its parent pattern will be guaranteed to match
267      * successfully once it reaches the step.
268      */
269   bool step_is_definite(uint byteOffset) @nogc nothrow
270   {
271     return ts_query_step_is_definite(tsquery, byteOffset);
272   }
273 
274   /**
275      * Get the name of one of the query's captures.
276      *
277      * Each capture is associated with a numeric id based on the order that it
278      * appeared in the query's source.
279      */
280   string capture_name_for_id(uint captureId) nothrow
281   {
282     uint len;
283     auto namePtr = ts_query_capture_name_for_id(tsquery, captureId, &len);
284     return namePtr[0 .. len].to!string;
285   }
286 
287   /**
288      * Get the name of one of the query's captures, given a TSQueryCapture.
289      */
290   string capture_name(TSQueryCapture capture) nothrow
291   {
292     return capture_name_for_id(capture.index);
293   }
294   /**
295      * Get the name of one of the query's string literals.
296      *
297      * Each string is associated with a numeric id based on the order that it
298      * appeared in the query's source.
299      */
300   string query_string_value_for_id(uint id) nothrow
301   {
302     uint len;
303     auto namePtr = ts_query_string_value_for_id(tsquery, id, &len);
304     return namePtr[0 .. len].to!string;
305   }
306 
307   /**
308      * Disable a certain capture within a query.
309      *
310      * This prevents the capture from being returned in matches, and also avoids
311      * any resource usage associated with recording the capture. Currently, there
312      * is no way to undo this.
313      */
314   void disable_capture(string captureName)
315   {
316     ts_query_disable_capture(tsquery, captureName.toStringz, captureName.length.to!uint);
317   }
318 
319   /**
320      * Disable a certain pattern within a query.
321      *
322      * This prevents the pattern from matching and removes most of the overhead
323      * associated with the pattern. Currently, there is no way to undo this.
324      */
325   void disable_pattern(uint patternId) @nogc nothrow
326   {
327     ts_query_disable_pattern(tsquery, patternId);
328   }
329 }